/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.web.gwt.form.server.service.impl;

import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.web.gwt.basics.client.exception.GwtBundledException;
import com.genia.toolbox.web.gwt.basics.client.i18n.GwtI18nMessage;
import com.genia.toolbox.web.gwt.basics.client.oracle.SimpleSuggestion;
import com.genia.toolbox.web.gwt.basics.process.ProcessException;
import com.genia.toolbox.web.gwt.form.client.form.Form;
import com.genia.toolbox.web.gwt.form.client.service.FormService;
import com.genia.toolbox.web.gwt.form.client.value.DataIdentifier;
import com.genia.toolbox.web.gwt.form.client.value.FormComplexValue;
import com.genia.toolbox.web.gwt.form.client.value.FormValues;
import com.genia.toolbox.web.gwt.form.server.provider.FormProvider;
import com.google.gwt.user.client.ui.SuggestOracle.Request;

/**
 * Implementation of the <code>FormService</code>. This service use Spring
 * context to retrieve all registered <code>FormProvider</code> and then
 * delegates all calls to the right provider.
 */
public class FormServiceImpl
    implements FormService
{

  /**
   * the <code>Log</code> variabe.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(FormServiceImpl.class);

  /**
   * I18n key for no such form error.
   */
  public static final String NO_SUCH_FORM_ERROR_KEY = "com.genia.toolbox.web.gwt.form.server.service.impl.FormServiceImpl.noSuchForm";

  /**
   * all <code>FormProvider</code> registered in the Spring context.
   */
  private transient Map<String, FormProvider> formProvidersByName = null;



  /**
   * get the display string for a given value.
   * 
   * @param formIdentifier
   *          the form identifier
   * @param fieldName
   *          the field name
   * @param value
   *          the current value of the field
   * @return the string to display for the given value
   * @throws GwtBundledException
   *           when an error occurred.
   */
  public String getDisplayString(final String formIdentifier, final String fieldName, final String value)
      throws GwtBundledException
  {
    try {
      return getFormProvider(formIdentifier).getDisplayString(fieldName, value);
    }
    catch (final BundledException e) {
      throw ProcessException.translateException(e);
    }
  }



  /**
   * retrieve a form knowing its identifier.
   * 
   * @param formIdentifier
   *          the form identifier
   * @param dataIdentifier
   *          the data identifier
   * @return the form corresponding to the identifier, or <code>null</code> if
   *         no such form exists.
   * @throws GwtBundledException
   *           when an error occurred.
   */
  public Form getForm(final String formIdentifier, DataIdentifier dataIdentifier)
      throws GwtBundledException
  {
    try {
      return getFormProvider(formIdentifier).getForm(dataIdentifier);
    }
    catch (final BundledException e) {
      throw ProcessException.translateException(e);
    }
  }



  /**
   * retrieve the initial value of the object that is edited.
   * 
   * @param formIdentifier
   *          the form identifier
   * @param dataIdentifier
   *          the data identifier
   * @return the initial value of the object that is edited
   * @throws GwtBundledException
   *           when an error occurred.
   */
  @SuppressWarnings("unchecked")
  public FormValues getIntialValue(final String formIdentifier, final DataIdentifier dataIdentifier)
      throws GwtBundledException
  {
    try {
      FormValues formValues = getFormProvider(formIdentifier).getInitialValue(dataIdentifier);
      if (formValues.getDataIdentifier() == null) {
        throw new NullPointerException("dataIdentifier must not be null for form with identifier " + formIdentifier + ".");
      }
      addParentIdenfiers(dataIdentifier.getParentIdentifiers(), formValues);
      checkForNullIdentifier(formValues, true);
      return formValues;
    }
    catch (final BundledException e) {
      throw ProcessException.translateException(e);
    }
  }



  /**
   * check that no null dataIdentifier is returned. If one is returned, issue a
   * warning.
   * 
   * @param formValues
   *          the {@link FormValues} being worked on
   * @param canBeNew
   *          whether the {@link FormValues} can be a newly generated one with
   *          <code>null</code> identifiers.
   */
  private void checkForNullIdentifier(FormValues formValues, boolean canBeNew)
  {
    if (!LOGGER.isWarnEnabled()) {
      return;
    }
    if (canBeNew && formValues.getDataIdentifier().getIdentifier() == null) {
      return;
    }
    checkForNullIdentifier(formValues);
  }



  /**
   * /** check that no null dataIdentifier is returned. If one is returned,
   * issue a warning.
   * 
   * @param formValues
   *          the {@link FormValues} being worked on
   */
  private void checkForNullIdentifier(FormValues formValues)
  {
    if (formValues.getDataIdentifier().getIdentifier() == null) {
      LOGGER.warn("formValues for form " + formValues.getFormIdentifier() + " was returned with a null identifier.");
    }
    for (Object formComplexValue : formValues.getFormComplexValues().values()) {
      List<?> values = ((FormComplexValue) formComplexValue).getValues();
      if (values != null) {
        for (Object subFormValues : values) {
          checkForNullIdentifier((FormValues) subFormValues);
        }
      }
    }
  }



  /**
   * hande the parent identifiers of the {@link FormValues} returned.
   * 
   * @param currentParentIdenfiers
   *          the current parent identifiers
   * @param formValues
   *          the {@link FormValues} being worked on
   */
  private void addParentIdenfiers(List<String> currentParentIdenfiers, FormValues formValues)
  {
    formValues.getDataIdentifier().setParentIdentifiers(currentParentIdenfiers);
    List<String> subParentIdentifiers = new ArrayList<String>(currentParentIdenfiers);
    subParentIdentifiers.add(0, formValues.getDataIdentifier().getIdentifier());
    for (Object formComplexValue : formValues.getFormComplexValues().values()) {
      List<?> values = ((FormComplexValue) formComplexValue).getValues();
      if (values != null) {
        for (Object subFormValues : values) {
          addParentIdenfiers(subParentIdentifiers, (FormValues) subFormValues);
        }
      }
    }
  }



  /**
   * get suggestions for a field.
   * 
   * @param formIdentifier
   *          the form identifier
   * @param dataIdentifier
   *          the data identifier
   * @param fieldName
   *          the field name
   * @param request
   *          the oracle request
   * @return the list of suggestion for the field
   * @throws GwtBundledException
   *           when an error occurred.
   */
  public List<SimpleSuggestion> getSuggestions(final String formIdentifier, final DataIdentifier dataIdentifier, final String fieldName, final Request request)
      throws GwtBundledException
  {
    try {
      return getFormProvider(formIdentifier).getSuggestions(dataIdentifier, fieldName, request.getQuery(), request.getLimit());
    }
    catch (final BundledException e) {
      throw ProcessException.translateException(e);
    }
  }



  /**
   * save the form on the server.
   * 
   * @param newValues
   *          the new values entered by the user
   * @return FormValues the new values that have been saved
   * @throws GwtBundledException
   *           when an error occurred.
   */
  @SuppressWarnings("unchecked")
  public FormValues saveForm(final FormValues newValues)
      throws GwtBundledException
  {
    try {
      FormValues returnedValues = getFormProvider(newValues.getFormIdentifier()).saveForm(newValues);
      if (returnedValues.getDataIdentifier() == null) {
        throw new NullPointerException("dataIdentifier must not be null for form with identifier " + newValues.getFormIdentifier() + ".");
      }
      checkForNullIdentifier(returnedValues, false);
      addParentIdenfiers(newValues.getDataIdentifier().getParentIdentifiers(), returnedValues);
      return returnedValues;
    }
    catch (final BundledException e) {
      throw ProcessException.translateException(e);
    }
  }



  /**
   * setter for the formProviders property.
   * 
   * @param formProviders
   *          the list of {@link FormProvider} to register
   */
  public void setFormProviders(Collection<FormProvider> formProviders)
  {
    formProvidersByName = new TreeMap<String, FormProvider>();
    for (final FormProvider formProvider : formProviders) {
      formProvidersByName.put(formProvider.getFormIdentifier(), formProvider);
    }
  }



  /**
   * returns the {@link FormProvider} associated to the given formIdentifier. It
   * throws an {@link GwtBundledException} if it cannot retrieve the
   * {@link FormProvider} either because the formIdentifier is <code>null</code>
   * or because there is no form associated to the given formProvider.
   * 
   * @param formIdentifier
   *          the identifier of the {@link FormProvider} to retrieve
   * @return the {@link FormProvider} associated to the given formIdentifier
   * @throws GwtBundledException
   *           if the {@link FormProvider} cannot be retrieved
   */
  private FormProvider getFormProvider(String formIdentifier)
      throws GwtBundledException
  {
    if (formIdentifier == null) {
      throw getNoSuchFormException(formIdentifier);
    }
    final FormProvider formProvider = formProvidersByName.get(formIdentifier);
    if (formProvider == null) {
      throw getNoSuchFormException(formIdentifier);
    }
    return formProvider;
  }



  /**
   * Create a new exception when a form is not found.
   * 
   * @param formIdentifier
   *          the formIdentifier of the inexistant form
   * @return an exception describing the error
   */
  private GwtBundledException getNoSuchFormException(final String formIdentifier)
  {
    return new GwtBundledException(new GwtI18nMessage(NO_SUCH_FORM_ERROR_KEY, new String[] { formIdentifier }));
  }

}
